from Goldenberry.optimization.ga.GbSelectorMgr.SelectionStrategy import SelectionStrategy
import random as ran
import numpy as np
import bisect
import copy as cp

class Roulette(SelectionStrategy):
    """Select parents using roulette selection.
    
    :param num_parents: number of parents to select (default 2)
    :param population: array list of individuals to compete for selection 
    :returns: array list of parents selected
    """
    def select_parents(self, num_parents = 2, population = None, costs = None):
        parents = []
        if costs.sum() == 0:
            costs = np.ones(costs)  # BORRAME! puse los costos de la poblacion en un np array
        comulative = np.cumsum(costs)
        for x in range(0, num_parents):
           try: 
               parents.append(population[bisect.bisect_left(comulative, ran.uniform(0, comulative[-1]))])
           except:
               # This section is called when the costfunctios has nevative values and the last value of the comulative vector is the last value of commutative function is less than the first value
               parents.append(population[ran.randint(0, len(comulative)-1)])
        return parents

class Tournament(SelectionStrategy):
    """Select parents using tournament selection.

    :param num_parents: number of parents to select (default 2)
    :param population: array list of individuals to compete for selection 
    :param tournament_size: size of the tornament (default 2)
    :returns: array list of parents selected
    """
    def select_parents(self, num_parents = 2, population = None, costs = None):
        tournament_size = 4
        parents = []

        for y in range(0, num_parents):
            best = population[ran.randint(0, len(population)-1)]
            for x in range(2, tournament_size+1):
                next = population[ran.randint(0, len(population)-1)]
                if next.cost > best.cost:
                    best = next
            parents.append(best)
        return parents

class Elitist(SelectionStrategy):
    """Select parents using elitism selection.

    :param num_parents: number of parents to select (default 2)
    :param population: array list of individuals to compete for selection 
    :returns: array list of parents selected
    """
    def select_parents(self, num_parents = 2, population = None, costs = None):
        pop = cp.copy(population)
        elite_size = int(num_parents * 0.2)
        parents = []
        if costs.sum() == 0:
            costs = np.ones(costs)  # BORRAME! puse los costos de la poblacion en un np array
        comulative = np.cumsum(costs)
        # Select no elite parents
        for x in range(num_parents - elite_size):
           try: 
               parents.append(population[bisect.bisect_left(comulative, ran.uniform(0, comulative[-1]))])
           except:
               # This section is called when the costfunctios has nevative values and the last value of the comulative vector is the last value of commutative function is less than the first value
               parents.append(population[ran.randint(0, len(comulative)-1)])
        # Select elite parents
        for x in range(elite_size):
            maxindx = np.argmax(costs)
            parents.append(pop[maxindx])
            del pop[maxindx]
            costs = np.delete(costs,maxindx)
        return parents
    